Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

Style & CSS


Accueil

Accueil CHM


Accueil

La page vide

Normal, dit comme ça,
vous ne risquez pas de comprendre.


ASM Mode Reel

 CHAPITRE 1 - Éléments de base
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            OUTILS NÉCESSAIRES
            AVERTISSEMENT
            ASSEMBLAGE D’INSTRUCTIONS  L’assembleur est un langage
            L’ARCHITECTURE D’UN ORDINATEUR
              - Le BUS de données
              - Le BUS d’adresses
              - Le BUS de contrôle
            STRUCTURE D’UN MICROPROCESSEUR INTEL
            DIAGRAMME INTERNE D’UN PROCESSEUR
            LES REGISTRES D’OFFSET
            LES REGISTRES DE SEGMENT
            LA PILE (STACK)
            LES INSTRUCTIONS DE BASE DU LANGAGE ASSEMBLEUR
            LES VARIABLES EN ASSEMBLEUR
            ADDRESSAGES POSSIBLES
              - L'adressage direct
              - L'adressage basé
              - L'adressage indexé
              - L'adressage basé et déplacement
            PUSH et POP
            Programme exemple #1: "Hello World!"

 CHAPITRE 2 - Utilisation de Turbo Assembler
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            INSTRUCTIONS DU MICROPROCESSEUR
            MODÈLES MÉMOIRES
              - Ébauche d'un programme .COM
              - Ébauche d'un programme .EXE
            ASSEMBLAGE DES PROGRAMMES
              - Assembling file:
            NOTIONS GÉNÉRALES DE PROGRAMMATION
              - Boucle FOR
              - Boucle WHILE
              - Structure CASE - SWITCH
            CODES SOURCES EXEMPLES
                asmch2p1.asm > Prgm demande le nom de l’usager version .COM
                asmch2p2.asm > Même prgm que asmch2p1.asm mais version .EXE
                asmch2p3.asm > Boucle FOR
                asmch2p4.asm > Boucle WHILE
                asmch2p5.asm > Boucle REPEAT
                asmch2p6.asm > Structure switch-case

 CHAPITRE 3 - Méthodes de programmation
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            ORGANISATION D’UN PROGRAMME
            AFFICHAGES DE MESSAGES
            AFFICHAGE DÉCIMAL
            POSITIONNEMENT DU CURSEUR
            LECTURE D’UNE CHAÎNE DE CARACTÈRES
            FONCTIONS ARITHMÉTIQUES ET LOGIQUES
                * L’addition ADD
                * L’incrémentation INC
                * La soustraction SUB
                * La décrementation DEC
                * La multiplication MUL
                * La division DIV
                * L’instruction NEG
                * L’instruction AND
                * L’instruction OR
                * L’instruction XOR
                * L’instruction NOT
                * L’instruction SHL (décalage logique à gauche)
                * L’instruction SHR (décalage logique à droite)
            RÉCAPITULATION

 CHAPITRE 4 - Accès aux périphériques
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                * FLAGS
                * SAUTS CONDITIONNELS
                * L'instruction CMP
                * Exemples pratiques  ( LODSB STOSB MOVSB )

 CHAPITRE 5 - Assembleur & C
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
                * Utilisation du compilateur interne
                * Inclure des routines de bas niveau dans le C
                * Modifications dans notre code C

 CHAPITRE 6  - Programme 100% Assembleur
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                * Optimisations
                * Les rendeurs de polygones
                * Le texture mapping
                * Feu.com
                * Procédure nombre aléatoire


ASM Mode Reel

 CHAPITRE 1 - Éléments de base
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Comme vous le savez peut être déjà, l’assembleur est un langage qui
 travaille directement sur les registres du processeur. Il est donc impératif
 de bien comprendre l’architecture et le fonctionnement interne des
 microprocesseurs d’ Intel avant d’entreprendre l’apprentissage du langage.
 Ce premier cours présentera donc très peu de code source et beaucoup de
 notions théorique. L’assembleur qui sera utilisé est Turbo Assembler.
 Une version minimale de ce logiciel, contenant l’assembleur tasm.exe et
 l’éditeur de liens tlink.exe,est disponible dans la section Téléchargements.

OUTILS NÉCESSAIRES
~~~~~~~~~~~~~~~~~~
 Pour programmer en assembleur, vous aurez besoin d'un compilateur asm
 Il existe deux principaux assembleurs sur le marché :
             - Turbo Assembler de Borland et Microsoft  Assembler.
 Vous pouvez également inclure des instructions assembleur directement dans
 votre code source C.En effet,la commande « asm » signifie au compilateur C
 que les instructions qui suivent sont de l’assembleur et de les traiteravec
 son assembleur interne. Remarquez qu’il est aussi possible d’indiquer au
 compilateur C d’utiliser Turbo Assembler plutôt que son compilateur interne,
 car ce dernier est beaucoup moins efficace. Dans le cas des compilateur de
 Borland, l’assembleur interne se nomme BASM.
 Également, vous aurez besoin d’une référence en-ligne très complète.
 Bien qu’elle commence à faire sentir son âge, l’excellente HelpPC constitue
 une véritable mine d’or d’informations sur la programmation système en
 assembleur. Il s’agit d’une sorte de d’index de toutes les instructions
 assembleur, ainsi que des interruptions et des informations sur les
 rocesseurs Intel. HelpPC est disponible dans la section Téléchargements.

AVERTISSEMENT
~~~~~~~~~~~~~
  Ce cours ne portera pas sur le set d’instructions de la série Pentium,
 AMD ou Cyrix. Le contexte du cours se limite aux instructions 80486, bien
 que l’emphase soit mis sur les registre 16-bits, et ce dans un but
 pédagogique. Il existe des différences entre MASM et TASM donc il est
 très possible que les codes sources ne compile pas sous MASM ou sur une
 version TASM antérieur à la version 5. Ce cours assume que le lecteur
 connaît les mathématiques Booléennes logiques (ET, OU, etc..). Finalement,
 le mode protégé et l’assembleur sous Win32 est définitivement hors de la
 portée de ce cours.

 *ASSEMBLAGE D’INSTRUCTIONS                     L’assembleur est un langage
 de programmation de bas niveau utilisant du code mnémonique pour remplacer
 les opérandes du processeur. Le but premier de l’assembleur est de remplacer
 des instructions binaires, du langage machine, vers une série de
 pseudo-instructions qui sont plus significatives qu’une simple série de
 chiffres binaires. Par exemple :
                                 1101 0110 0111 1110 0000 0010
                                 MOV          AX             2
 La série de bits ne vous dit probablement rien, mais même si on ne connaît
 pas l’assembleur, on pourrait déduire, par l’instruction mnémonique « MOV »
 (move, déplace) que cet commande place la valeur 2 dans le registre AX.
 Quand nous assemblons des instructions assembleur, le compilateur
 transforme nos instructions en langage machine, une série de 1 et de 0,
 qui lui sera interprété par le processeur.

L’ARCHITECTURE D’UN ORDINATEUR
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 L’architecture d’un ordinateur se compose principalement d’un processeur
 central, d’un bus, des port d’entrée/sortie et la mémoire. C’est à
 l’intérieur du microprocesseur central que toutes les traitements se font.
 Les données et les instructions machines sont stockés en mémoire jusqu'à
 ce qu’elles soient réclamés par le CPU. Ce dernier peut lire et écrire des
 données sur des périphériques par l’entremise des ports d’ I/O. Ces
 transfert s’effectue par le BUS système.
 Il existe 3 types de BUS sur le PC :

  - Le BUS de données

    Les processeurs 8086 utilise le bus de données pour transporter les
    données entres les divers composants d’un système. La taille de ce bus
    varie selon l’architecture. Il se compose généralement de 16, 32 ou
    64 lignes. Un bus de 16 lignes ne limite pas le processeurs aux
    instructions 16 bits : cela signifie simplement qu’il peut recevoir
    16 bit par cycle machine. Donc une données sur 32-bit prendrait 2
    cycles machine pour être reçu par le processeur. De la même façon,
    un bus 32-bit peut séparer une instruction 32-bit en 2 de 16, et
    répartir le transfert sur 2 cycles. Donc, plus le bus est large,
    plus les données transit rapidement et on peut accéder à plus de
    mémoire par opération.
    PRIVATE  Microprocesseur Taille du bus de données
    808888086168028616803861680386dx3280486328058664

  - Le BUS d’adresses

    Le bus de données transporte les données, mais la question demeure :
    où? Le bus d’adresse contient la réponse à cette question.
    Afin de faire la différence entre les emplacement en mémoire vive et
    les périphériques, on désigne chaques adresse par un numéro unique.
    Quand un programme veut accéder à un périphérique quelconque,
    il dépose sur le bus de données son adresse et ce périphérique saura
    qu’on fait appel à lui, car ce numéro est unique. Le nombre de ligne
    du bus d’adresse détermine le nombre d’adresse possibles. En effet,
    s’il était composé de 2 lignes, il pourrait composer 2 seuls adresse
    uniques : 0 et 1. C’est donc en utilisant des puissances de deux qu’on
    peut calculer l’adressage maximal du bus :

    PRIVATE  Processeur Taille du bus d’adresse Mémoire adressable
    8088201,048,5768086201,048,576802862416,777,21680386sx2416,
    777,21680386dx324,294,976,29680486324,294,976,29680586324,
    294,976,296

 - Le BUS de contrôle
    Le bus de contrôle est une série de signaux électriques qui contrôle
    les communications entre le processeur et le reste du système. Il
    contrôle les lectures et les écritures, et sa structure dépasse le
    cadre de ce cours.

 STRUCTURE D’UN MICROPROCESSEUR INTEL
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Au coeur d’un microprocesseur se trouvent les registres, sortes de
    variables matérielles.
    Le processeur y charge les données en provenance de la  mémoire,
    les traite à l’aides des instructions machines puis les
    renvoies dans la mémoire. Ces manipulations sont extrêmement rapides
    car les registres sont implantés à l’intérieur même du processeur et
    leur contenu est donc accessible sans l’aide du bus. Il existe
    plusieurs types de registres :
    - registres généraux
    - registres de segment
    - registres d’offset
    - registres d'états
    Chacun de ces registres ont une fonctions spécifique, bien qu’il soit
    possible de les détourner de leurs buts premier. Selon la famille de
    processeur, ces registres ont une taille de 8, 16 ou 32-bits. Les
    registres 32-bit sont apparut seulement depuis le 80386 et leurs nom
    est précédé de la lettre « E ». Voici un tableau avec les différents
    registres du processeur.

 DIAGRAMME INTERNE D’UN PROCESSEUR
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    LES REGISTRES GÉNÉRAUX
    Au nombre de 4, ils ont un usage bien précis bien qu’ils peuvent être
    utilisé comme zone de stockage intermédiaire :

 (E)AX  Appelé « accumulateur », car il est souvent utilisé pour les
         opérations arithmétiques.
 (E)BX  Appelé « registre de base », car il est utilisé pour l’adressage
        basé (voir ci-dessous)
 (E)CX  le « compteur » car il sert pour l’exécution des boucles.
 (E)DX  Appelé « data » car il contient les adresses d’I/O quand il faut
        accéder des données sur le bus.
    Notons que chacun de ces registres peut être décomposé en deux registres
    de 8-bits, qu’on peut accéder individuellement.

 LES REGISTRES D’OFFSET
 ~~~~~~~~~~~~~~~~~~~~~~
         Ces registres contiennent une valeur représentant un offset à
         combiner à une adresse de segment. Il sont au nombre de 5 :
 (E)SI  -Appelé « source index ». Principalement utilisé lors d’opérations
         sur les chaînes de caractères (comme pointeur sur la source);il est
         associé au registre de segment DS
 (E)DI  -Appelé « destination  index». Utilisé lors d’opération sur les
         chaînes de caractères (comme pointeur sur la destination) et
         normalement associé au registre de segment DS.
         Associé à ES s’il traite des chaînes de caractères.
 (E)IP  -Appelé « instruction pointer ». Ce registre est associé au registre
         de segment (CS :IP) pour indiquer la prochaine instruction à
         exécuter. Ce registre ne pourra jamais être modifier directement.
 (E)BP  -Appelé « Base pointer ». Associé au registre de segment SS (SS :BP)
         pour accéder aux données de la pile lors d’appels de sous-programme
         (utilisé pour appelé des fonctions externes)
 (E)SP  -Appelé « stack pointer ». Associe au registre de segment SS (SS :BP)
         pour indiquer le dernier élément de la pile.

LES REGISTRES DE SEGMENT
~~~~~~~~~~~~~~~~~~~~~~~~
      Ces registres servent à sélectionner différents blocs(segments)
   de mémoires. Un registre de segment pointe au début d’un segment dans
   la mémoire. En mode réel, une adresse mémoire est formée d’un segment
   et d’une offset. C’est par l’utilisation des registres de segment
   combinés aux registres d’offset que l’on peut accéder les différentes
   zones de la mémoire conventionnelle. Un segment 8086 en mode réel ne
   peut pas être plus grand que 65536 octets. Cette limites à dérangé de
   nombreux programmeurs depuis très longtemps.

   Ces registres sont au nombre de 4 :
 - CS : « Code segment », il contient l’adresse du segment de la mémoire
   où se trouve les instructions (code) du programme. Remarquer qu’un
   programme peut être plus grand que 64k, il suffit de spécifier plusieurs
   segments de code. En changeant le segment CS, nous changeons de segment
   de code et nous pouvons en continuer l’exécution à partir de là.
 - DS : « Data segment », il contient l’adresse du segment de mémoire
   contenant les données définies dans le programme. De la même façon
   que CS, on peut utiliser plus que 64k de mémoire pour les données d’un
   programme.
 - SS : « Stack segment », il contient l’adresse de la pile (voir plus bas).
 - ES : « Extra segment », contient l’adresse d’un segment supplémentaire
   pouvant recevoir des données.

LA PILE (STACK)
~~~~~~~~~~~~~~~
   En plus de registres, l'ordinateur dispose d'une pile. C'est une
   zone mémoire qui sert à sauver temporairement des informations.
   Elle possède une structure LIFO, c'est à dire last in first out,
   similaire à une pile de plateau à la cafétéria : le dernier plateau
   placé, donc celui sur le dessus de la pile, sera le premier pris.
   Sur la pile, nous empilons le contenu des registres généraux 16-bit.
   Nous verrons quels instructions utilise la pile dans la section suivante.

LES INSTRUCTIONS DE BASE DU LANGAGE ASSEMBLEUR
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Avant de présenter quelques instructions de base du langage assembleur,
   voyons ensemble la syntaxe qui prévaut lorsqu’on écrit une source en
   assembleur. Premièrement, l’assembleur se fout complètement de la case
   des caractères, donc le majuscule et le minuscule est accepté. Règle
   générale, on s’entend pour écrire les commandes en majuscules. On doit
   séparé les commandes entre elles, d’au moins un espace. Les opérandes
   sont séparés par une virgule et dans ce cas un espace est facultatif :
        MOV AX, 0
        MOV AX,   0
        MOV   AX,  0
        MOVAX,0        ; incorrect
  On insère des commentaires dans le code avec le point virgule « ; ».
  Il existe naturellement des mots réservés inutilisables par le programmeur
  les mnémoniques d’instructions, les directives d’assemblage, les registres
  et les mots (BYTE,WORD,DWORD).

LES VARIABLES EN ASSEMBLEUR
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Les variables      sont des zones mémoires qui sont réservées lors de
  l'assemblage. Ils fonctionnent de la même façon que les variables des
  langages de haut niveau. Voici comment déclarer des variables en asm

-DB
  Cette instruction définit et initialise des zones mémoires sur un octet,
  le début de la zone mémoire sera repéré par un identificateur, à notre
  choix. Équivalent à « char » en C.
  Exemple:
            chaine  db « Ceci est une chaîne$ »  ; une chaîne a affichée
            nombre  db 0                         ; il faut '$' à la fin.
            inconnu db (?)       ; la variable n’a pas de valeur initiale
-DW
  Cette instruction définit et initialise des zones mémoires sur un mot,
  c'est à dire 16 bit. C’est l’équivalent de « int » en C.
  Exemple:
            trois     dw 1000,2000,3000
            adresse   dw 0FFFFh
-DD
  Cette instruction définit et initialise des zones mémoires sur un double
  mot, c'est à dire 32-bit. Équivalent à « float » en C.
  Exemple:
            trois     dw 1000,2000,3000
            adresse   dw 0FFFF000Ch
- Les labels
  Quand nous déclarons une variable, son nom pointe sur l’adresse mémoire
  où elle est stockée. De la même façon, nous pouvons placer des préfixes
  devant des instructions. Ce principe est souvent utilisé par les boucles.
  Exemple:
        debut:
        MOV AX, 0 ; l'instruction MOV est associé au label début
        MOV BH, 5 ;
        JMP debut ; effectue une boucle au label "debut"
        QUELQUES INSTRUCTIONS

  Comme nous le savons,l'instruction doit spécifier deux éléments importants:
    L’opération à réaliser (addition, soustraction, décalage,…)
    Les données manipulées par l’instructions.
    Nous verrons que ces données sont dans les registres ou en mémoire

    Pour accéder à une donnée en mémoire, le microprocesseur devra connaître
    son adresse ET le nombre d’octets (bytes) qu’elle occupe.

    MOV  Une des instructions les plus utilisées. Elle permet de manipuler
    différents types d'opérandes dont des zones de mémoires. Tout simplement,
    le contenudel'opérande de droite est transféré dans l'opérande degauche.

    Exemples:
         MOV AH, 12   ; AH <-- 12 (AL n'est pas affecté)
         MOV BX, 1    ; BL & BH sont affectés
         var1 DB ?    ; définition d'une variable BYTE
         var2 DW ?    ; définition d'une variable WORD
         MOV var1,12  ; opération sur 1 BYTE
         MOV var2, 1  ; opération sur 1 WORD

    Note : quand le type d'opérande est indéfini, il est possible que
    l'instruction ne permettre pas à l'assembleur de déterminer le type des
    opérandes. L'instruction suivante:

    MOV data[BX],1 sera donc complétée comme suit:
    MOV BYTE PTR data[BX],1  ; pour indiquer que l'opérande est de type BYTE

    La commande MOV nous amène à un sujet important de la programmation asm
    les différents modes d’adressages de la mémoire. En effet, il existe
    différentes possibilités de présentation d'une adresse.
    Nous allons les analyser en détails car elles peuvent être présentes
    dans la plupart des autres instructions de l’assembleur.

* adresse mémoire

    On spécifie une adresse mémoire sous la forme suivante :

                          [Segment : Offset]

    Il serait évidemment très fastidieux d’avoir à spécifier un segment et
    une offset pour chaque zone de mémoire. C’est pour cela que la plupart
    du temps, on assume que le registre DS est le segment de données par
    défaut.     Ainsi, à la place de :
                                        MOV AX,DS:[BX]
                                        MOV AX,DS:[SI+2]
    On pourra tout simplement écrire :
                                        MOV AX,[BX]
                                        MOV AX,[SI+2]

    Nous verrons plus tard que les chaînes de caractères sont traitées de
    façon différentes. On peut naturellement outrepasser cette règle en
    spécifiant nous-même le segment :
                                        MOV AX,CS:[BX]

ADDRESSAGES POSSIBLES
~~~~~~~~~~~~~~~~~~~~~
    La commande MOV est utilisé dans cette série d’exemple car elle permets
    les 8 types d'adressage sur des données ou des sur emplacements mémoire:

- L'adressage immédiat

        Registre <----- Valeur immédiate
        Le contenu du registre est initialisé par une valeur immédiate,
        bref une constante.

        EXEMPLE:                transfert de 8 bits (1 octet)
                  MOV AH,12   ; AH <-- 12, AL n'est pas affecté
                                transfert de 16 bits (2 octet ou 1 mot)
                  MOV BX,1    ; BX <-- 1, BL et BH sont affectés

- L'adressage direct

        Registre <------ mémoire (adressage direct)
        Le contenu d'un registre est initialisé par le contenu de la zone
        mémoire repérée par l'identificateur.
        L'opération se fait sur 8 ou 16 bits.

        EXEMPLE:
                 nb1 DB 5     ; zone d'un octet
                 nb2 DW 7     ; zone d'un mot
                 MOV AH, nb1  ; AH <-- 5 opération sur 8 bits
                 MOV BX, nb2  ; BX <-- 7 opération sur 16 bits
                 MOV AL, nb2  ; Erreur (8 bits/16 bits)

- L'adressage basé

       Registre <------ [BX] adressage basé sur DS

       Initialise le contenu d'un registre avec la zone mémoire située à
       l'adresse DS:[BX]. Dans ce cas, [BX] représente le contenu de la
       zone mémoire dont l'adresse (offset) est dans BX.

       EXEMPLE:
                           ; AX <-- contenu de la zone mémoire à l'adresse
              DS:0008
              MOV BX,8
              MOV AX,[BX]
                           ; AX <-- contenu de la zone mémoire à l'adresse
                           ; DS:nb1, donc 5
              DW nb1 5
              MOV BX, offset nb1    ; BX <-- offset de nb1
              MOV AX,[BX]

- L'adressage indexé

       Registre <------ [SI] ou [DI] adressage indexé
       Ce mode d'adressage est principalement utilisé pour les chaînes de
       caractères ou pour isoler un élément d'un tableau. ES:DI ou DS:SI

     EXEMPLE:
                ;contenu de la zone mémoire à l'adresse DS:0
                MOV SI,0
                MOV AX,[SI]

              L'adressage indexé et basé
              Registre <------ [BX][DI] adressage indexé et basé DI
              Registre <------ [BX][SI] adressage indexé et basé SI
              Ce type d'adressage utilise BX et, SI ou DI, la base étant DS.
              On l'apelle basé car on l'utilise souvent pour référencer les
              éléments d'un tableau dont l'adresse de base est dans BX.

     EXEMPLE:
              ; AH <-- contenu de la mémoire à l'Adresse DS:7 (5+2)
              MOV BX,2
              MOV SI,5
              MOV AH,[BX][SI]

- L'adressage basé et déplacement

              Registre <------  donnee[BX]
              Registre <------ [BX+donnee]
     EXEMPLE:
              Tableau DW 5 DUP (10,20,30,40,50)
              ; AX <--- troisième élément du tableau donc 30
              MOV BX,2
              MOV AX,Tableau[BX]
              L'adressage indexé et déplacement

              Registre <------ donnée[SI]
              Registre <------ donnée[DI]
     EXEMPLE:
              ; AX <-- contenu de la zone mémoire adresse DS:4+1
              MOV SI,4
              MOV AX,[SI+1]
              L'adressage basé, indexé et déplacement

              Registre <-- donnée[BX][SI]
              Registre <-- donnée[BX][DI]
     EXEMPLE:
              ; AX <-- contenu de la zone mémoire adresse DS:2+5+4+1
              db1 DB 2
              MOV BX,5
              MOV SI,4
              MOV AX,nb1[BX][SI+1]

       Toutes ces différentes façon d’accéder aux données seront reprises
    dans les descriptions des autres instructions de l’assembleur.
    Reportez-vous à cette table pour vous rafraîchir la mémoire si vous
    en ressentez le besoin.

PUSH et POP
~~~~~~~~~~~
    Les deux seules opérations sur
    la pile sont l'empilage (PUSH) et le désempilage (POP).
    La pile nous permet de sauvegarder temporairement les données.
    Lorsqu'une instruction PUSH est rencontrée, SP est d'abord décrémenté
    de 2 (chaque élément = 2 octets), et le contenu du registre spécifié
    est copié dans l'élément numéro SP de la pile. Pour une opération POP,
    l'élément numéro SP de la pile est copié dans le registre spécifié et SP
    est incrémenté de 2.
   Exemple:
          Pile de x éléments, vide:
          100 SP --> SP
          pointe "avant" la pile
          98 | | ; vide
          96 | | ; vide
          On exécute les instructions suivantes:
          MOV AX,1 ; AX reçoit la valeur 1
          MOV BX,2 ; BX reçoit la valeur 2
          PUSH AX ; on empile AX
          PUSH BX ; on empile BX
          On aura le résultat suivant:
          100 ; SP pointe sur 2
          98 | 1 | ; 1er élément
          96 SP-> | 2 | ; 2ième élément (LIFO)
          94 | | ; 3ième élément vide
          Maintenant, on exécute ceci:
          MOV BX,3 ; BX reçoit 3
          POP BX ; BX reçoit le dernier élément du stack (3 est écrasé)
          Le stack aura l'air de cela:
          100 ; SP pointe sur 2
          98 SP-> | 1 | ; 1er élément
          96 | 2 | ; 2ième élément (LIFO)
          94 | | ; 3ième élément vide
          NB: Lors du prochaine PUSH, 2 sera écrasé.
          INT
      Cette instruction appelle l’interruption dont le numéro est
      spécifié par l’opérande.Les interruptions sont utilisé par les
      périphériques pour signaler au processeur d’effectuer un arrêt
      afin qu’il puisse s’occuper d’eux. Il ne faut pas oublier de
      remplir de façon correcte les registres (généralement AX) avec
      les bons numéros d’interruptions.
      La liste complète des interruptions du BIOS se trouve facilement
      sur le net, en voici donc un bref aperçu :
        Numéro Fonction 0D Disque dur
                        0E Disquette
                        0F Imprimante
                        10 Fonctions vidéos
                        11 Configuration système
                        12 Taille de la RAM
                        13 Fonctions disquette/disque dur
                        14 Accès à l’interface série
                        16 Interrogation du clavier

* Programme exemple #1: "Hello World!"
  Il est maintenant le temps pour vous d'écrire votre premier programme
  en assembleur. Nous verrons plus en détails l’utilisation de TASM au
  prochain chapitre. Pour l’instant, concentrez-vous sur les instructions
  que nous avons vue dans ce premier chapitre.
    Ce fameux programme,utilisé par Lionel Ritchie,le créateur du langage C,
  s'adapte facilement en assembleur. Une chaîne de caractère est un ensemble
  d'octets, donc nous déclarons une variable DB, que nous initialisons à
  "Hello World!". Quand une chaîne de caractère est destinée à l'affichage,
  il faut ajouter impérativement le '$'. Ensuite, nous appelons la fonction
  09h de l'interruption 21h. Cette fonction reçoit en paramètre le numéro
  09h dans AH, ainsi que l'adresse de segment de la chaîne dans DS, et
  l'offset dans DX. Cette chaîne de caractères peut également contenir des
  codes ASCII (comme un retour de ligne).

     .MODEL SMALL                ; Modèle mémoire
      STACK 100h                 ; Taille de la pile
     .DATA                       ; Les donnees debute ici
     chaine DB "Hello World!$"   ; Initialise la variable "chaine"
                                 ; Ne pas oublier le $ à la fin
     .CODE                       ; Le code débute ici.
     START:                      ; Label du debut du programme
     MOV AX, @DATA               ; Initialise le segment des données DS
     MOV DS, AX                  ; avec le contenu de DATA (chaine)
     MOV DX,offset chaine        ; DX pointe sur la chaîne
     MOV AH,09h                  ; Service 9h de l'interruption 21h
     INT 21h                     ; On appelle l'interruption 21h (DOS)
     MOV AX, 4C00H               ; Fonction de retour
     INT 21H                     ; au système d'exploitation
                                 ; On appelle l'interruption 21h (DOS)
     END START                   ; Fin du programme

  Vous n’avez qu’a recopier ce code dans votre éditeur de texte favori, et
  de le sauvegarder avec l’extension .asm. Ensuite, vous exécutez
  tasm.exe [nomfichier], et tlink.exe [nomfichier].
  Le programme exécutable sera ainsi créé.
  Je n’expliquerai pas ici toutes les subtilités du code.
  Au prochain chapitre nous verrons plus en détails les particularités
  de Turbo Assembler. Ce cours tire maintenant à sa conclusion.
  Il se peut que le lecteur se sente légèrement dépassé par toute la
  matière lors de la première lecture; il s’agit de ne pas se décourager
  et de lire tranquillement chaques sections. En résumé, nous avons vue
  au cours de ce chapitre :
  Le fonctionnement interne d’un ordinateur
  Les registres du processeur
  Les segments de mémoire et la pile
  Les variables DB, DW et DD ainsi que les labels
  Les instructions assembleur MOV, INT, PUSH et POP
  Les modes d’adressage en assembleur
  Les interruptions du BIOS


ASM Mode Reel

 CHAPITRE 2 - Utilisation de Turbo Assembler
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

     L’assembleur TASM possède un éventail de fonctionnalités qui nous
   facilite grandement la vie lorsqu’on écrit des programmes. En effet,
   il propose, entres autres, un modèle simplifié de segmentation qui
   élimine du codage fastidieux. Nous étudierons en détails la structure
   d’un programme .COM et d’un .EXE, afin de bien comprendre les
   différences entres ces deux modèles.

 INSTRUCTIONS DU MICROPROCESSEUR
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     Par défaut, l’assembleur assume que nous travaillons sur un micro-
   processeur 8086. Nous sommes donc limités dans le set d’instructions.
   Cependant, il est possible de spécifier le type de microprocesseur
   utilisé, par la simple command .[processeur]
   Par exemple :
        .186     Permet l’assemblage d’instructions du 80186
        .286     Permet l’assemblage d’instructions du 80286
        .386     Permet l’assemblage d’instructions du 80386
        .486     Permet l’assemblage d’instructions du 80486
 MODÈLES MÉMOIRES
 ~~~~~~~~~~~~~~~~
     Si vous vous reportez au chapitre précédent, vous remarquerez que
   la première instruction du programme est la suivante :
        .MODEL SMALL
     Cette commande indique à l’assembleur que le modèle mémoire de ce
   programme est le modèle « small ». Le mode réel des PC se divise en six
   modèles mémoires basés sur la taille des données et du code. La raison
   est principalement pour l’adressage. En effet, le programme doit savoir
   s’il utilisera des pointeurs NEAR ou FAR. En résumé, un pointeur NEAR
   utilise seulement la portion « OFFSET » lorsqu’il adresse la mémoire;
   donc, il ne peut qu’adresser 1 seul segment de 64K. Les pointeurs FAR
   combine SEGMENT et OFFSET pour former des adresses de 20-bit,permettant
   d’adresser la totalité de la mémoire conventionnelle. Il va sans dire
   que les pointeurs FAR sont plus lent que les pointeurs NEAR. Voici la
   liste de ces modèles:

       - Tiny       : Code + Données < 64k (pour les fichiers .COM)
       - Small      : Code < 64k  Données < 64k
       - Medium     : Code > 64k  Données < 64k
       - Compact    : Code < 64k  Données > 64k
       - Large      : Code > 64k  Données > 64k
       - Huge       : Tableaux supérieurs à 64k

   Le modèle Tiny est le seul qui permet la création de fichiers exécutable
   avec l’extension .COM. Dans ce cas, notre programme en entier peut être
   contenu dans un seul et unique segment de 64k. Sinon, le programme que
   nous assemblerons aura l'extension .EXE. Maintenant que nous connaissons
   les différents modèles mémoires et leurs raisons d’êtres, étudions la
   structure typique d’un programme .COM et .EXE.

 Ébauche d'un programme .COM
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Dans le cas d'un programme .COM, toutes les données sont contenus dans
  1 seul segment,donc pas besoin de spécifié où CS et DS débutent puisqu'ils
  sont tous contenus dans le même.Également, les programmes .COM ne possèdent
  pas de pile, donc le SS est inutilisé. Remarquez aussi que les données se
  situe juste avant les instructions, et qu’on utilise l’instruction de saut
  JMP, pour débuter le programme au début des instructions.
    Voici le squelette d’un programme .COM typique sans utilisation de
  segmentation simplifiée :

       .MODEL TINY
       .486
       ASSUME CS:CODE,DS:CODE        ; Code dans même segment
       ASSUME ES:NOTHING,SS:NOTHING  ; ES et SS ne sont pas utilisé
       CODE SEGMENT                  ; Segment de code
       ORG 100H                      ; Adresse de début du programme
       DEBUT: JMP START              ; Début du programme principal

       START:                        ; Début des instructions
       PUSH CS                       ; Initialise
       POP DS                        ; le segment de données
                       ; Code ici
       MOV AX,4C00H                  ; Termine le programme
       INT 21H                       ; par une fonction de DOS
       CODE ENDS                     ; Fin du segment
       END DEBUT                     ; Référence le point d'entrée

    Cependant,TASM nous offre un modèle simplifié pour déclarer nos segments.
   L’instruction .STARTUP gère le saut JMP que nous devions effectuer dans la
   version précédente. CODESEG et DATASEG initialise pour nous les segments,
   donc plus besoin de PUSH CS et POP DS et des ASSUME.
   Le tout est donc un code légèrement plus lisible :
       .MODEL TINY              ; modèle mémoire tiny
       .486
       CODESEG                  ; Initialise le segment de code
       ORG 100h                 ; Point d’entrée du programme
       .STARTUP                 ; Début des instructions

       MOV AX,4C00H
       INT 21H
       DATASEG          ; Initialise le segment de données (PUSH CS POP DS)

       END              ; Fin

 Ébauche d'un programme .EXE
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Dans le cas d'un programme .EXE, la macro DOSSEG nous permets d'utiliser
   la même segmentation que le DOS utilise, c'est à dire ASSUME CS:CODE,
   DS:DATA, ES:DATA. Il faut également définir une pile, avec la commande
   STACK suivit de la taille de la pile désirée.
   Pour ce faire, MOV AX, @DATA, suivit de MOV DS,AX.
   Voici un squelette d'un programme EXE:

       DOSSEG                   ; Modèle de segmentation de DOS
       .MODEL SMALL             ; Petit modèle mémoire
       .STACK 100H              ; 256 Octets pour la pile
       .DATA

       .CODE
       START
       MOV AX,@DATA             ; Initialise le
       MOV DS,AX                ; segment de données

       MOV AX,4C00H             ; Fin du programme
       INT 21H
       END START

 ASSEMBLAGE DES PROGRAMMES
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 Une fois notre programme terminé, nous sommes prêts à tester notre code.
   Pour ce faire, on tape la commande tasm , où "code" est le nom du
   programme à assembler. Si tout va bien, le compilateur nous retourne
   quelques choses de semblable:
       c:\asm>tasm prog1.asm

 Assembling file:
 ~~~~~~~~~~~~~~~~
  prog1.ASM
  Error messages: None Warning messages:None Passes:1 Remaining memory: 416k
    c:\asm>
  Nous obtenons alors le fichier de liens prog1.OBJ, qui est prêt à être
  linker. Le programme TLINK, fournit avec TASM, fait ce travail.
  Si nous voulons produire un .COM, il faut spécifier l'option /T, qui
  transforme le fichier OBJ en COM.
  Sinon, le linker retournera une erreur:

  Pour un .COM
    c:\asm>tlink /t prog1
     Turbo Link Version 7.0 Copyright (c) 1996 Borland International

  Pour un .EXE
    c:\asm>tlink prog1
     Turbo Link Version 7.0 Copyright (c) 1996 Borland International

 NOTIONS GÉNÉRALES DE PROGRAMMATION
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  L'assembleur n'est pas un langage de programmation pour les débutants.
   Il est donc fort possible que vous ayez déjà programmer en C ou en PASCAL.
   Les mêmes méthodes de programmation peuvent s'appliquer en assembleur.
   Le langage assembleur supporte les même structures que les langages de
   haut niveau, tant pour les types de données complexes (RECORD, STRUCT)
   que pour les principes de bases (boucles, conditions, etc).

 * Boucle FOR
   Cette boucle est une des plus utilisée. Nous utiliserons un label pour
   identifier le début de la boucle et l'instruction CMP, qui compare deux
   valeur. Si cette valeur est atteinte, la boucle peut se terminer, donc
   nous allons sauter avec JA, qui effectue le saut si supérieur. Voici un
   simple extrait d’un programme qui affiche le code ascii entre 41h et 7Ah
   à l’aide de la fonction du DOS 02h, écriture d’un car :

          MOV CL, 41h           ; CL contient 41h ('A')
   for:   CMP CL, 7Ah           ; Plus grand que 7Ah
          JA endfor             ; si oui, fini
          MOV DL,CL             ; mettre le car dans DL (pour affichage)
          MOV AH,02h            ; fonction d'ecriture d'un car 02h
          INT 21h               ; interruption DOS
          INC CL                ; on incremente CL
          JMP for               ; on recommence
   endfor:

  L'instruction JMP fait retourner le fil d'exécution au label for, donc
   au début de la boucle. A chaque itération, la commande INC augmentera
   la valeur contenu dans CL de 1. On compare la nouvelle valeur ainsi de
   suite jusqu'à la fin de la boucle.

 * Boucle WHILE
   De la même façon, la boucle while s'adapte avec des label.
   La seule véritable différence est que la condition de sortie modifier
   le type de saut qu'on devra utiliser. Par exemple, on veut écrire un
   programme qui converti les caractères minuscules en majuscule,
   immédiatement après qu'une touche aie été enfoncée, et ce jusqu'à ce que
   l'utilisateur appuie sur ESCape. Notre condition de sorti sera si la
   touche ESC, code ASCII 27, est enfoncée. La commande JZ effectue un saut
   si la valeur du test est 0. Comme CMP effectue sa comparaison par
   soustraction, cela est idéal.
   Le seul hic, c'est que la fonction d'écriture du caractère modifie le
   contenu de AL. Nous allons donc devoir protéger AL, en utilise la Pile.

   While:
           MOV AH,08h          ; fonction de lecture d'un car
           INT 21h             ; interruption DOS
           CMP AL, 27          ; si ce caractère est pas ESC
           JZ Quit             ; quitte le programme
           PUSH AX             ; on sauvegarde AL
           MOV DL,AL           ; mettre le car dans DL (pour affichage)
           MOV AH,02h          ; fonction d'ecriture d'un car
           INT 21h
           MOV DL,124          ; mettre le car dans DL (pour affichage)
           MOV AH,02h          ; fonction d'ecriture d'un car
           INT 21h
           POP DX              ; on ramene AL de la pile dans DL
           SUB DL,32           ; soustraire 32 de son code ASCII
           MOV AH,02h          ; fonction d'ecriture d'un car
           INT 21h
           MOV AH,9            ; pour faire les espaces
           MOV DX, OFFSET CRLF
           INT 21h
           JMP While
           Quit:
  Notre programme bouclera tant que la touche ESC ne sera pas enfoncée.
   Avant d'imprimer le caractère lu, nous effectuons un PUSH du registre AX,
   qui sauvegarde notre registre AL, que nous ramenons dans DX avant d'en
   faire l'affichage en majuscule (Code ASCII - 32).
   Boucle REPEAT
   Question de mettre un peu de soleil dans votre journée, pourquoi ne pas
   remplir votre écran en mode texte de petit bonhomme sourire? La boucle
   repeat n’est pas plus complexe que les autres :
           MOV BX, 2000
           Repeat:
           MOV DL,02
           MOV AH,02h
           INT 21h
           DEC BX
           CMP BX,0
           JG Repeat

 *Structure CASE - SWITCH
   Ce prochain programme appelle une fonction du BIOS pour avoir le jour
   courant. Le seul hic est que cette fonction retourne un nombre, soit 0
   pour dimanche, 1 pour lundi, etc. Pas très utile, donc nous allons
   utilisez un switch pour déterminer quel chiffre correspond avec quel
   chaîne de caractères. Nous allons parcourir chaque choix, et aussitôt
   que nous trouvons notre jour, nous quittons le switch.

       MOV AH,2Ah     ; Fonction DOS 2Ah (retourne des infos sur la date)
       INT 21h        ; Interruption DOS (le jour est dans AL)
  switch:             ; plus clair avec label
     Case1:
       CMP AL,0       ; CMPare AL avec 0
       JNZ Case2      ; non? alors on continue a l'autre case...
       MOV DX, OFFSET Dimanche
       JMP Endswitch
     Case2:
       CMP AL,1
       JNZ Case3
       MOV DX, OFFSET Lundi
       JMP Endswitch
     Case3:
       CMP AL,2
       JNZ Case4
       MOV DX, OFFSET Mardi
       JMP Endswitch
     Case4:
       CMP AL,3
       JNZ Case5
       MOV DX, OFFSET Mercredi
       JMP Endswitch
     Case5:
       CMP AL,4
       JNZ Case6
       MOV DX, OFFSET Jeudi
       JMP Endswitch
     Case6:
       CMP AL,5
       JNZ Case7
       MOV DX, OFFSET Vendredi
       JMP Endswitch
     Case7:
       MOV DX, OFFSET Samedi
 Endswitch:
       MOV AH,9        ; Fonction d'affichage d'une chaîne de car
       INT 21h
       MOV AX,04ch
       INT 21h
       Dimanche   DB  "Dimanche$"
       Lundi      DB  "Lundi$"
       Mardi      DB  "Mardi$"
       Mercredi   DB  "Mercredi$"
       Jeudi      DB  "Jeudi$"
       Vendredi   DB  "Vendredi$"
       Samedi     DB  "Samedi$"

  Nous déclarons nos chaînes de caractères séparément.
  La condition JNZ saute si le résultat n'est pas 0, donc si ce n'est pas
  le bon résultat.Le reste du code est répétitif et se comprend par luimême.

CODES SOURCES EXEMPLES

  Ce chapitre contient de nombreux codes sources exemples.
  Le code est bien commenté, et si vous avez des questions n’oubliez pas
  de vous reporter à HelpPC, votre compagnon essentiel dans le monde de
  l’assembleur. Voici donc la liste des programmes exemples :

  asmch2p1.asm > Programme qui demande le nom de l’usager version .COM
  asmch2p2.asm > Même programme que asmch2p1.asm mais version .EXE
  asmch2p3.asm > Boucle FOR
  asmch2p4.asm > Boucle WHILE
  asmch2p5.asm > Boucle REPEAT
  asmch2p6.asm > Structure switch-case

  Tous ces programmes compilent sous TASM, sans aucun problèmes.
  N’oubliez cependant pas l’option /T de TLINK pour les programme .COM.
  Nous avons vues plusieurs nouvelles instructions assembleur dans ce cours,
  notamment les instructions de saut. Elles sont très nombreuses et je ne
  les expliquerai pas en détails chacune. En résumé, nous avons vu :
      Les modèles mémoires
      La  segmentation simplifiée de Turbo Assembler
      Les boucles FOR, WHILE et REPEAT
      La  structure de décision switch-case
      Les instructions CMP, INC, DEC, SUB
      Les instructions de saut JZ, JNZ, JA


ASM Mode Reel

 CHAPITRE 3 - Méthodes de programmation
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   Dans ce chapitre, nous effectuerons une bonne révision des concepts vues
  dans les deux premiers chapitres, afin de s’assurer d’avoir bien compris
  les principes fondamentaux de l’assembleur. Par la suite, nous entre-
  prendrons d’écrire nos fonctions d’affichages et de traitements communs,
  pour créer notre première « librairie » assembleur de fonctions.

 ORGANISATION D’UN PROGRAMME
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
   Il existe plusieurs méthodes pour organiser un programme, la meilleur
  consiste à découper le programme en une série de sous-programmes
  (fonctions et procédures). Tous les exemples de ce chapitre seront
  présentés sous la forme de sous-procédures. Nous inclurons le fichier
  contenant nos procédures, de la même façon qu’en C nous utilisons les
  bibliothèques standard avec « #include  ». Rappelons que, par
  convention, les sous-programmes doivent sauvegarder les registres qu’ils
  utilisent (PUSH et POP avant d’effectuer un retour, ou USES). Comme nous
  le verrons au chapitre 5, certaines autres étapes viennent se rajouter
  quand on désire créer des fonctions assembleur qui seront utilisé par des
  compilateurs de haut niveau.

 AFFICHAGES DE MESSAGES
 ~~~~~~~~~~~~~~~~~~~~~~
   Le système d’exploitation MS-DOS mets à notre disposition le servie 09h
  de l’interruption 21h pour afficher des chaînes de caractères, comme nous
  l’avons vu dans les chapitres précédent. Nous allons créer une procédure
  qui va grandement nous simplifier la vie :

   Affiche le message contenu dans DS:DX
   AFFICHE PROC NEAR
             PUSH AX        ; Sauve AX sur la pile
             MOV AH, 09h    ; Fonction 09h
             INT  21h       ; Interruption DOS 21h
             POP AX         ; Ramène AX
             RET            ; Retour
             AFFICHE ENDP

   Nous remarquons tout d’abord qu’une procédure se déclare ainsi :

           PROC [NEAR] [FAR], et se termine par  ENDP.

   Il est très important de ne pas oublier l’instruction RET qui rend le
   contrôle au programme principal. Si un programme possède beaucoup de
   messages, il serait utile de pouvoir transmettre simplement le numéro
   du message. Voici comment procéder :

   Table  DW mess0      ; mess0 = offset du message 0
          DW mess1      ; mess1 = offset du message 1
          DW mess2      ; mess2 = offset du message 2
          mess0 DB ‘Bonjour je suis le message 1$’
          mess1 DB ‘Bonjour je suis le message 2$’
          mess2 DB ‘Bonjour je suis le message 3$’
                        ;Affiche le message selon le numéro dans AL
    AFFICHE PROC NEAR
          PUSH BX       ; on peut utiliser USES AX, BX
          PUSH AX       ; et laisse faire les PUSH et POP
          MOV AH,0      ; Mets AH à 0 pour le décalage
          SHL AX, 1     ; Multiplie le no du message par 2 pour                                         ; obtenir un offset dans la table
          MOV BX,OFFSET table
                        ; BX pointe sur le début de table
          ADD BX, AX    ; avance BX sur l’offset du message
          MOV DX, [BX]  ; DX<- contenu de l’élément de table
                        ; pointé par BX
          MOV AH, 09h   ; service 9 de l’interruption 21h
          INT  21h      ; affiche le message
          POP AX
          POP BX        ; Ramène AX et BX
          RET           ; Ne pas oublier RET!
    AFFICHE ENDP

  Quand nous appelons cette procédure, il suffit de mettre dans le registre
   AL le numéro du message que l’on désire afficher sur l’écran.

  Si l’instruction SHL vous intimide, continuez la lecture de ce chapitre,
   une section entière est dédié pour ce sujet.

AFFICHAGE DÉCIMAL
~~~~~~~~~~~~~~~~~
  Jusqu'à présent, vous remarquerez que nous avons seulement travaillé
   avec des chaînes de caractères.
  La raison en est bien simple : l’assembleur ne dispose d’aucune méthode
   pour afficher des nombres à l’écran,nous devons donc créer une procédure.
   Pour cela, il faudra décomposer le nombre et imprimer un par un les
   chiffres qui le composent. Nous aurons donc besoin de 2 procédures :
   affichage d’un chiffre et affichage d’un nombre.
  La fonction 2 de l’interruption 21h est prévue pour afficher à l’écran
   un caractère contenu dans DL. Nous devons donc convertir le chiffre en
   son code ascii en ajoutant 48 :

 Code Ascii
   Caractère 48 0
             49 1
             50 1
   Maintenant, nous devons décomposer le nombre à afficher.
   Nous savons que la divison (DIV) divise un WORD (numérateur dans AX)
   par une opérande sur 8 bits (dénominateur) et installe le quotient
   dans AL et le reste dans AH, ou divise un double mot dans DX AX et
   installe le quotient dans AX et le reste dans DX.
    Par exemple :
                     8765 DIV 10 = 876   reste 5
                     876  DIV 10 = 87    reste 6
                     87   DIV 10 = 8     reste 7
                     8    DIV 10 = 0     reste 8

   Nous constatons que les restes sont les chiffres composant le nombre.
   Nous ne pouvons pas afficher le reste à chaque division car sinon le
   nombre serait à l’envers, il faut donc sauvegarder temporairement ces
   valeurs sur la pile, ce qui nous arrange bien car le dernier empilé sera
   le premier affiché et le premier empilé le dernier, et de cette façon le
   nombre sera correct.

    Attention, bien que le nombre à afficher soit sur16 bits, nous devons
    « simuler »  une division sur 32 bits.

   En effet,dans l’exemple précédant 876 ne rentre pas dans AL (max 255),
   nous allons donc diviser le nombre dans DX,AX par 10, le quotient sera
   placé dans AC mais DX sera chaque fois remis à 0 avant la division
   puisque le numérateur est toujours sur 16 bit (dans AX).
    Affiche un nombre en décimal non signé dans AX

   AffDecimal PROC NEAR
        PUSH BX            ; Sauvegarde BX
        PUSH CX            ; Sauvegarde AX
        MOV BX,10          ; BX sera notre dénominateur
        MOV CX, 0          ; Le compteur de chiffre
   Decompose :
        XOR AX,AX          ; AX est remis a 0
        DIV BX             ; Calcule DX,AX/BX le reste dans DX
        PUSH DX            ; Sauve le chiffre sur la pile
        INC  CX            ; Incrémente le compteur de chiffre
        CMP AX, 0          ; Quotient=0?
        JNZ Decompose      ; Jump NonZero (saut si pas 0)
                           ; Affichage des chiffres
   Affichage :
        POP AX             ; Récupère un chiffre sur la pile
        CALL AffChiffre    ; Procédure qui affiche un chiffre
        LOOP Affichage     ; CX = CX – 1 (Loop tant que CX >0)
        POP CX
        POP BX
        RET
   AffDecimal ENDP

    Le principe est le même pour les autres bases, tel hexadécimal, binaire
   et octal. Il suffit de changer le dénominateur par le nombre représentant
   la base désirée. Cette procédure ne traite cependant pas les nombres
   signés et les nombres à virgule flottante.

 POSITIONNEMENT DU CURSEUR
 ~~~~~~~~~~~~~~~~~~~~~~~~~
    La position du curseur est importante car elle influence les procédures
   d’affichage et de lecture du clavier.
   Cette procédure appelle leservice 02h de l’int 10h (vidéo) du BIOS.
    Positionne le curseur à la colonne DL et à la ligne DH

    GotoXY PROC NEAR
                PUSH BX
                PUSH DX
                MOV AH,2
                MOV BH,0
                INT 10h
                POP DX
                POP BX
                RET
    GotoXY ENDP

 LECTURE D’UNE CHAÎNE DE CARACTÈRES
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Pour lire une chaîne de caractères, nous pouvons appeler l’int 21h,
  service 0Ah qui installe les caractères tapés dans une zone repérée
  par DS:DX (buffer).Il faudra donc définir une zone de données (buffer)
  dans le programme qui recevra les caractères.La fonction se termine
  quand l’usager appuie sur ENTER.

  Le buffer contient alors les informations suivantes :
  Octets    Contenu
    0       Valeur indiquant le nombre maximum de caractères à lire,
              cette valeur doit être installée AVANT l’appel de la fonction.
    1       Nombre de caractères lus(sans compter le RETURN),
              cette valeur est installée par la fonction
    2       A partir de cette position, on trouve les caractères lus.

  tampon   DB 255 DUP (?) ;Zone mémoire tampon contenant jusqu'à 255 car
                          ; Lit AL caractères dans DS :DX

  LireChaine PROC NEAR
             PUSH BX
             PUSH CX
             MOV BX,DX    ; BX <- offset du buffer
             PUSH [BX]    ; préserve les deux premiers octets du buffer
             MOV [BX], AL ; installe le nombre de caractères à lire
                          ; en position 0 du buffer
             MOV AH,0Ah   ; fonction 0Ah, lire une chaîne de car
             INT  21h     ;                                  car = caractére
             POP CX
             POP BX
             RET
  LireChaine ENDP

  Il faudra bien entendu assigner DX sur l’offset du tampon qui recevra les
 caractères avant d’appeler cette fonction. Si l’usager essaie d’entrer plus
 de caractère qu’il est permis (contenu dans AL), l’int 21h émet un  beep.

FONCTIONS ARITHMÉTIQUES ET LOGIQUES
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  Nous allons maintenant aborder les instructions arithmétiques ainsi que
   les principes d’addition binaire, de soustraction, de négation, etc.
   Les instructions de base sont naturellement :
   l’addition, la soustraction, la multiplication et la division.

* L’addition ADD
   Cette instruction additionne deux quantités numériques sur 8 ou 16 bits,
   le résultat pouvant être placé en mémoire ou dans un registre.

* L’incrémentation INC
   Ajoute 1 au registre spécifié, le résultat obtenu est donc identique à
   celui d’un ADD avec 1 comme seconde opérande.

* La soustraction SUB
  Soustrait une quantité d’une autre, le résultat pourra être placé en
  mémoire ou dans un registre.

* La décrementation DEC
  Décremente de 1 la valeur du registre spécifié, le résultat est donc
  identique à celui d’un SUB avec 1 comme seconde opérande.

* La multiplication MUL
  Multiplie l’opérande spécifiée par la quantité contenu dans le registre
  AX ou AL (accumulateur). Le résultat est placé dans AH et AL si les
  opérandes étaient des octets et dans DX et AX si les opérandes étaient
  des mots.

* La division DIV
  Divise un mot (dénominateur dans AX) par une opérande sur 8 bits
  (dénominateur) ou divise un double mot (dans DX,AX) par une opérande
  sur 16 bits. Le quotient est placé dans AL et le reste dans AH si
  l’opérande était un octet, et le quotient dans AX et le reste dans DX
  si c’était un mot.

* L’instruction NEG
  Cette instruction calcule le complément à 2 de l’opérande spécifié.
  Rappelons que pour obtenir le complément à 2 d’une valeur,
  il faut d’abord inverser tous les bits et ajouter 1.La technique
  du complément à 2 obtient la représentation binaire d’un nombre.

* L’instruction AND
  L’instruction AND effectue un ET logique sur les deux opérandes
  spécifiées, le résultat est placé dans la première opérande.
  Rappelons qu’un ET logique donne le résultat 1 si les deux opérandes sont
  1 et donne 0 dans les autres cas.Les combinaisons possibles sont donc
        0 AND 0 = 0
        0 AND 1 = 0
        1 AND 0 = 0
        1 AND 1 = 1
  Cette instruction est souvent utilisés pour masquer des bits.

* L’instruction OR
  Effectue un OU logique (Inclusif) sur les opérandes spécifiés,le résultat
  est placé dans la première opérande. Rappelons qu’un OU logique donne un
  résultat 0 si les deux opérandes sont 0 et donne 1 dans les autres cas.
  Les combinaisons possibles sont donc :
        0 OR 0 = 0
        1 OR 0 = 1
        1 OR 1 = 1
        0 OR 1 = 1

* L’instruction XOR
  Effectue un OU logique (Exclusif) sur les opérandes spécifiés,le résultat
  est placé dans la première opérande. Rappelons qu’un OU Exclusif donne un
  résultat 1 si les deux opérandes sont différentes et donne 1 dans les
  autres cas.
  Les combinaisons possibles sont donc :
        0 XOR 0 = 0
        1 XOR 0 = 1
        1 XOR 1 = 0
        0 XOR 1 = 1

* L’instruction NOT
  Effectue un NON logique sur l’opérande spécifiée; le résultat est placé
  dans l’opérande. Rappelons qu’un NON logique inverse (complémente) la
  valeur d’un bit.
  Les deux combinaisons possible sont donc :
        NOT 0 = 1
        NOT 1 = 0

* L’instruction SHL (décalage logique à gauche)
  Cette instruction déplace les bits d’un registre ou d’un emplacement
  mémoire d’un nombre de positions vers la gauche. Les positions libérées
  sont remplies avec des 0. Il s’agit d’un moyen rapide d’effectuer des
  multiplications par des puissances de 2.

* L’instruction SHR (décalage logique à droite)
  Cette instruction déplace les bits d’un registre ou d’un emplacement
  mémoire d’un nombre de positions vers la droite. Les positions libérées
  sont remplies avec des 0. Il s’agit d’un moyen rapide d’effectuer des
  divisons par des puissances de 2.

 RÉCAPITULATION
 ~~~~~~~~~~~~~~
 En conclusion, voici une liste aide mémoire pour vérifier que vous avez
  bien compris les concepts énoncés dans les trois premiers chapitres.
  Si vous éprouvez des difficultés, il serait sage de relire certains
  passages concernant les notions en questions.
  En voici la liste :
 [ ] permet de spécifier une zone mémoire plutôt qu’un registre directement
 Une instruction ne peut jamais contenir 2 opérandes mémoires :
      MOV [BX], [SI]  ; incorrect
      MOV Id1, [BX]   ; incorrect
 Certaines instructions possèdent plusieurs variantes,il n’est pas possible
  d’en utiliser d’autres. Si on souhaite exécuter une opération non prévue,
  il faudra contourner le problèmes avec des instructions prévues :
      MOV ES, DS      ; incorrect
      MOV AX, DS
      MOV ES, AX      ; correct
 Si le type des opérandes ne permets pas à l’assembleur de déterminer
  s’il s’agit d’une opération sur un BYTE ou WORD, il faudra compléter
  avec BYTE PTR ou WORD PTR.
 Lorsqu’on travaille sur des chaînes de caractères, l’instruction REP
  décrémente automatiquement le registre CX (voir exemple plus bas)
 L’instruction LOOP décrémente le registre CX automatiquement


ASM Mode Reel

 CHAPITRE 4 - Accès aux périphériques
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 Si l’assembleur n’est plus utilisé comme outil de développement dans
  l’industrie, il reste que sa fonctionnalité primaire, celle d’interagir
  avec la machine, est toujours exploitée. En effet plusieurs langages de
  haut niveau n’ont pas dans leurs librairies standard la capacité
  d’interroger les ports d’entrées/sorties, l’imprimante, les disques durs
  et autres périphériques. L’assembleur reste donc une solution très
  puissante dans ces cas. Dans ce chapitre, nous verrons quelques exemples
  de programmation assembleur des périphériques. Comme nous serons souvent
  appelé à tester l’état de ces périphériques, nous verrons également plus
  en détails les différentes opérations de saut conditionnels possibles.

*FLAGS
~~~~~~~
  Les flags, "indicateurs", sont des bits qui regroupés dans le registre
  de flag.Ces flags sont modifiés en fonction de l'exécution des différentes
  instructions. Ils sont utilisés en relation avec les opérateurs de
  comparaisons, que nous allons étudier par la suite.

  - CF  Le Carry Flag est celui qui contient la retenue d’une opération,
        lorsque le résultat dépasse la capacité du registre. Le bit est
        alors placer dans le CF.
  - PF  Il s'agit du Parity Flag.
        Le flag est 1 si le nombre de bits d'une opérande est pair.
  - AF  AF est l'auxiliary carry qui s’apparente au CF.
  - ZF  Zero Flag mis à 1 lorsque un résultat est égal à 0.
        Souvent utilisé pour les diverses opérations, il est utile pour
        éviter des problèmes de divisions sur 0.
  - SF  Sign Flag passe à 1 si nous avons un résultat signé (négatif)
  - IF  IF pour Interrupt Flag, enlève la possibilité au processeur de
        contrôler les interruptions.
  - Si  IF=0,le processeur ne commande pas et si  IF=1 c'est le contraire
  - DF  Direction Flag. C'est ce Flag qui donne l'indication sur la manière
        de déplacer les pointeurs lors des instructions de chaîne
        (MOVSB,STOSB) soit positivement, soit négativement.
  - OF  Overflow Flag (indicateur de dépassement). Il permet de trouver et
        de corriger certaines erreurs produites par des instructions
        mathématiques.

* SAUTS CONDITIONNELS
~~~~~~~~~~~~~~~~~~~~~
   Dans la programmation de périphériques, comme dans tous les programmes,
  il faut souvent avoir recours aux sauts pour déterminer le chemin à suivre
  dans notre programme. Par exemple, si on veux savoir si l’imprimante est
  connectée, il faut interroger le port parallèle et agir soit en
  initialisant l’imprimante, ou en avisant l’usager avec un message d’erreur.

  JB - JNAE - JC     Below - Not Above or Equal - Carry      CF=1

  JAE - JNB - JNC    Above or Equal - Not Below - Not Carry  CF=0

  JE - JZ            Equal - Zero                            ZF=1

  JNE - JNZ          Not Equal - Not Zero                    ZF=0

  JO - JNO           Overflow - Not Overflow                 OF=1 - OF=0

  JP - JPE           Parity - Parity Even                    PF=1

  JNP - JPO          No Parity - Parity Odd                  PF=0

  JS - JNS           Signed - Not Signed                     SF=1 - SF=0

  JA - JNBE          Above - Not Below or Equal              CF=0 et ZF=0

  JBE - JNA          Below or Equal - Not Above              CF=1 ou ZF=1

  JG - JNLE          Greater - Not Less or Equal             ZF=0 et SF=OF

  JGE - JNL          Greater or Equal - Not Less             SF=OF

  JL - JNGE          Less - Not Greater or Equal             SF (signé)=OF

  JLE - JNG          Less or Equal - Not Greater     ZF=1 ou SF (signé)=OF

  * L'instruction CMP
      Pour tester tout cela, nous avons besoin d'une instruction,
     c'est CMP qui nous aidera à le faire. Cette instruction va nous servir
     à tester différentes valeurs et modifier les flags en fonction du
     résultat. CMP est un SUB qui ne change ni la source ni la destination,
     seulement les flags. Un CMP BX,CX sera comme un SUB BX,CX à l’exception
     près que BX ne sera pas modifié. Si BX=CX alors BX-CX=0 donc le flag ZF
     sera égal à 1. Si nous voulons faire un saut avec "égal à" (JNE ou JZ
     qui demande ZF=1), nous avons ZF=1 et comme JZ demande que ZF=1 pour
     faire le saut, nous avons donc le saut.

  * Exemples pratiques
     Les exemples de ce chapitre démontre l’application pratique de ces
     opérateurs de saut conditionnels. Dans le premier exemple, on vérifie
     si une souris est présente, et on quitte le programme si ce n’est pas
     le cas. Remarquez dans le deuxième programme, qui démontre la
     programmation de l’imprimante, j’utilise l’opérateur TEST, qui effectue
     lui un ET logique sur les opérandes, mais qui va modifier les Flags.
     Dans ce même exemple, je présente aussi brièvement le traitement des
     chaînes des chaînes de caractères. En effet, il existe une série
     d’opérateur spécifiques pour les chaînes de caractères et leurs
     traitements. Voici un résumé de ces fonctions :

     LODSB      Charge dans AL le contenu de DS :SI. SI et DI sont
                incrémentés ou décrémentés selon la valeur de DF.
     STOSB      Mets dans ES :DI le contenu de AL. SI et DI sont
                incrémentés ou décrémentés selon la valeur de DF.
     MOVSB      Mets dans ES :DI le contenu de DS :SI. SI et DI sont
                incrémentés ou décrémentés selon la valeur de DF.

     Toutes ces instructions viennent aussi en format Word (16-bit) et Dword
     (32-bit). On remplace les deux dernières lettres, donc LODSW, STOSW,
     MOVSW, LODSD, STOSD et MOVSD. Ces instructions sont souvent utilisés
     en programmation graphique pour effectuer des transferts rapides entre
     les écrans virtuels, les sprites et l’écran physique.

     Résumé :
             Les indicateurs d’états (FLAG)
             L’instruction CMP et TEST
             Les instructions de chaînes LOD, STO et MOV (version SB,SW,SD)


ASM Mode Reel

 CHAPITRE 5 - Assembleur & C
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

L'inclusion de modules en assembleur dans les programmes de haut niveau
constitue un objectif fondamental de la programmation en assembleur. Si les
langages de haut niveau présentent beaucoup d'avantages et sont plus
agréables à manier que l'assembleur, il leur arrive de ne pas connaître
certaines fonctions indispensable à un projet. Également, pour le traitement
des boucles interne (inner loops), il est carrément impératif d'utiliser
l'assembleur.Il existe 3 façon d’inclure de l’assembleur dans nos sources C.
un peut avoir recours à l’assembleur interne du compilateur, utiliser un as-
sembleur externe directement dans notre source ou à l’extérieur decelui-ci.

* Utilisation du compilateur interne

  Tous les compilateurs modernes ont un assembleur interne qui permet
    d’inclure directement dans du code source des lignes de codes asm.
    En Borland C++, il s’agit de l’instruction asm.
    Nous avons déjà vu sont utilisation, comme par exemple

    void mode_graphique()
                           {
                            asm
                               {
                                MOV AX, 0x13
                                INT 0x10
                               }
                           }

 L’assembleur interne est cependant beaucoup moins efficace qu’un assembleur
 externe tel MASM ou TASM.Je n’expliquerai pas ici en détails tous les
 routines traduites en assembleur, car à présent vous devriez être capable
 de suivre le déroulement d’une fonction assembleur à partir du code source.
 J’ai néanmoins commenté le code pour vous aider dans cette tâche.

* Inclure des routines de bas niveau dans le C
  Ce processus s'effectue en 2 étapes: premièrement, il faut coder les
  fonctions elles-mêmes dans un fichier source ASM. Ensuite, il faut que
  le compilateur C puisse y référer.Cependant,il faudra légèrement modifier
  notre code asm pour que nos fonctions soient à même d'être adressées et
  encore plus important, qu'elles puissent recevoir des paramètres.

  Tous les langages de haut niveau ont une exigence commune vis-à-vis du
  module en assembleur: ils réclament l'installation d'un cadre de pile.
  Il s'agit en fait d'un dédoublement du segment de pile dans le registre BP.
  Comme la pile du langage de haut niveau de doit pas être modifier, le
  contenu du pointeur de pile est copié en BP. Avant cela, l'ancienne valeur
  de BP est sauvegardée sur la pile.
  Donc, tous modules assembleurs destinés à être incorporé dans du code de
  haut niveau doit commencer par les instructions:

  PUSH BP
  MOV  BP,SP

  Également, chaques fonctions doit être déclarée PUBLIC, c'est à dire la
  rendre accessible à tous les modules du projet. Pour restaurer la pile à
  son état original, il ne faut pas oublier l'évidence:
  POP BP
  Le nom de notre fonction dans le module assembleur devra être précédé
  du caractère de soulignement, _ ("underscore").
  La commande ARG nous permet de spécifier les paramètres que notre fonction
  recevra. Par exemple, notre fonction pour choisir un mode vidéo par le BIOS
  deviendrait, si on veut l'utiliser avec un module externe, la suivante:

  PUBLIC _setmode         ; Utilisable par tous les modules
  _se  tmode PROC         ; PROCédure setmode (avec le _)
  ARG mode:WORD           ; Paramètre "mode"
  PUSH BP                 ; sauvegarde bp
  MOV BP,SP               ; sur la pile
  MOV AX, mode            ; ax = mode
  INT 10h                 ; interruption vidéo
  POP BP                  ; on ramène la pile originale
  RET                     ; toujours mettre RET
  _setmode ENDP           ; fin de la procédure

 * Modifications dans notre code C
  Pour compiler correctement notre code dans l'environnement C, il faudra
  créer un fichier project. Pour ce faire, dans le menu Project, il faut
  ouvrir un nouveau projet, avec Open Project. Donnez un nom à votre projet,
  par exemple, test. Un nouveau fichier PRJ sera créé. Cette fois, dans le
  menu PRJ, aller sur Add Item. Ajouter votre fichier .ASM, ainsi que votre
  fichier CPP. Donc:
  PROJECT --> OPEN PROJECT
  PROJECT --> ADD ITEM -> "fichier.ASM"
  PROJECT --> ADD ITEM -> "fichier.CPP"
  La fenêtre "Project" devrait apparaître en bas de l'écran avec la liste des
  fichiers que vous avez inclus.Pour rendre accessible les modules Assembleur
  il faut déclarer nos fonctions avec le mot-clef "extern". Cela signifie que
  la définition de la fonction se trouve dans un module exterieur au C.
  Par exemple:
  extern "C"
  {
   void setmode(unsigned int mode);.
   etc...
   }
  Maintenant, vous êtes prêt à compiler. Assurez-vous que TASM.EXE et
  TASM2MSG sont tous les deux présents dans votre PATH (pour être sûr,
  copier ces 2 fichiers dans c:\windows\command). Si tout ce passe bien,
  votre code asm sera compilé par TASM, et le CPP par le compilateur C.
  Si l'environnement Borland vous envoie un message style "Cannot find
  executable : TASM", il faut que vous spécifier le chemin de tasm.exe:
  OPTIONS --> TRANSFER...
  EDIT --> ~Turbo Assembler
  PATH --> (le chemin), ex: C:\TC\BIN\
  Invoquer un assembleur externe sans utiliser le fichier projet
  Il est possible d’utiliser de l’assembleur beaucoup plus simplement,
  en l’incluant directement dans le code source. Il existe en effet une
  instruction du pré-processeur qui signale que l’assembleur interne doit
  faire place à un assembleur externe pour le traitement des lignes de
  codes assembleur. Il s’agit de #pragma inline.
   Par exemple :
  // Exemple d'appel d'inline assembly par TASM
  #pragma inline
  asm .386p
  asm MESSAGE DB "Allo de Turbo Assembleur dans C++$",13,10

  void main()
   {
    asm MOV AX, 3
    asm INT 0x10
    asm MOV AH, 9
    asm XOR AL,AL
    asm MOV DX, OFFSET MESSAGE
    asm INT 0x21
   }

* Programmes exemples
  Les programmes exemples mettent en pratique les différentes façon
  d’appeler de l’assembleur dans des programmes C. Règle générale,
  l’assembleur interne est plus facile d’utilisation mais est moins
  efficace que l’assembleur externe.


ASM Mode Reel

 CHAPITRE 6  - Programme 100% Assembleur
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 L'effet de feu que j'ai présenté dans la section effets est réalisé en C.
 Évidemment, cela simplifie la compréhension. Mais qu'en est-il de la
 performance et de la taille du code? Pour ce qui est de la vitesse, rien
 d'extraordinaire. La taille? Environ 37820 octets. Pas si mal, non? Non.
 En fait c'est carrément minable. Vous allez comprendre après avoir vu une
 routine de feu 100% assembleur.

*Optimisations
 Il y quelques années encore, l'utilisation de l'assembleur était impératif.
 On ne pouvait pas concevoir d'écrire une demo ou un jeu dans un langage de
 haut niveau. Mais depuis quelques années,les compilateurs se sont grandement
 amélioré, et le besoin d'optimiser s'est quelque peu estompé.
 La raison en est simple: coder en assembleur demande du temps.
 Souvent, sur le marché, le temps de création d'un jeu est vital, surtout
 quand on compétionne avec d'autres concurrents pour un jeu du même genre.
 Par exemple, en 1993, après la sortie de Doom, une compagnie avait voulu
 sortir un jeu de raycasting similaire mais complètement en assembleur.
 Le jeu roulait a 35 fps sur un vieux 80286, certes. Mais le jeu ne vit
 jamais le jour et la compagnie fit faillite. Telle est la réalité du monde
 du développement de jeu (et le monde et général).. le temps est de l'argent.
 Par mon expérience, je crois que les parties qui mérite d'être optimisé sont
 surtout les boucles internes, ce qui est appelé à chaque trame du programme,
 par exemple:
* Les rendeurs de polygones
* Le texture mapping
  Routines de "blit" (transfert de mémoire vidéo)
  Mais encore, rien ne peut remplacer de la bonne logique de programmation.
  Aussi puissant que le compilateur peut être, il n'est pas meilleur que son
  programmeur. Il est possible qu'un débutant crée du code assembleur plus
  lent que le compilateur aurait généré.Il faut donc bien analyser la logique
  de l'algorithme avant de convertir le tout en assembleur.
  Souvent l'algorithme peut être accélérer en "déroulant les boucles",
  ou en changeant carrément d'approche.

Regardons à présent les avantages que présente un programme coder
  entièrement en assembleur.

*Feu.com
  Taille : 182 octets
  Vraiment impressionnant, non? J'ai tout simplement traduit l'algorithme
  de feu présenté auparavant, avec quelques modifications.
  C'est sur ces quelques détails que je vais apporter votre attention.
  Mon premier obstacle fût de trouver une fonction de nombre aléatoire en
  assembleur. Heureusement, une petite recherche sur Internet m'en retourne
  une bonne dizaine. J'en choisi une, celle qui me semble la plus simple et
  directe. Elle retourne une valeur aléatoire dans DX:

* Procédure nombre aléatoire
  Random PROC
    MOV AX,[RandSeed]
    MOV DX,8405h
    MUL DX
    INC AX
    MOV [RandSeed],AX
  RET
  Random ENDP

    Comme nous voulons des valeurs entre 0 et 255, nous n'utiliserons que la
    partie Low de DX, soit DL. Merveilleux, non? Cette fonction nous permets
    de créer le "foyer" du feu, c'est à dire la ligne inférieur du feu.
    L'algorithme de feu reste le même. Pour déterminer la couleur d'un pixel,
    n fait la moyenne des 4 pixels environnants,
    soit x+1, x-1, y-1 et le pixel x,y lui-même.
    MOV AL, DS:[SI] ; x+(x-1)+(x+1)+(y+320)/4
    ADD AL, DS:[SI+1] ; x-1
    ADC AH, 0
    ADD AL, DS:[SI-1] ; x+1
    ADC AH, 0
    ADD AL, DS:[SI+320] ; y-1
    ADC AH, 0
    SHR AX,2 ; division par 4
    ADC nous permets de faire une addition avec la retenu, car si nos valeurs
    dépasse AL (255), ce registre ne suffit plus à contenir la variable.
    L'excédant se retrouve dans le carry, que ADC nous permets d'ajouter au
    total.Au moment d'afficher nos pixels, nous attendons d'abord une retrace
    verticale. Sans elle, le feu est vraiment rapide mais beaucoup moins
    esthétique. Par contre, la taille du feu descends à 160 octets.
    Je préfère cependant la beauté d'un effet à la vitesse absolue.
    La copie du tableau de DS:SI vers ES:DI (Data sur Screen, ES vers A000h)
    s'effectue avec l'instruction MOVSD, Move String Double, qui déplace 1
    double (soit 2 word, ou 4 octets) de... DS:SI vers ES:DI.
    Même si la dernière ligne est calculée, je ne l'affiche pas, car les
    valeurs aléatoires y sont générés, ce qui donne une ligne "hétérogène".
    J'ai commenté le code de A à Z, donc il ne devrait pas avoir deproblèmes.

 * L'assembleur est un outil indispensable pour réaliser les effets les plus
    spectaculaires.J'insiste cependant pour que vous vous procuriez un livre
    sur le sujet, car ce petit cours n'est qu'une brève introduction sur ce
    vaste sujet, trop souvent oublié par les programmeurs de haut niveau.